﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson.Serialization.Attributes;
using Ymatou.PerfMonitorClient;
using Ymatou.User.Dto.Auth;
using YmtAuth.Common.Utility;
using YmtSystem.CrossCutting;
using YmtAuth.Common;

namespace YmtAuth.Domain.Model.YmatouUser
{
    public class UserToken : Entity<string>
    {
        private static readonly object lockObj = new object();
        /// <summary>
        /// 私钥
        /// </summary>
        private const string privateKey = "<RSAKeyValue><Modulus>yJlVOeahWIm8qv0Q65fyl58OVhyd3UrkwpfH2lyxsHvHzOJmaeP5BiPcG0/7wxdiqDvAFzYNrLdkgBfBcvyl9OzAOTV25wQPygRAzydfqclFP/Qsx1BnzBp/RvjNIyqKavaqpVijAAhaCfFE+Gv22FeT9PkFEYmuDK82cTIyYFs=</Modulus><Exponent>AQAB</Exponent><P>8XeN5KteWvmgT2EewQ4VO9OiC4rlacA5fHQJpV+E59JpmGxlcrLFhBolgOaeGgD5Tt1py68ZluDIzYg4hfRm/w==</P><Q>1KwYzaq7Pp9pplnliv/7jXffu+sQH3a9VGvLbXfLZHNsi+50VdYj1O3D7MLSqI9WNINIQoEOUDfer5W7uyICpQ==</Q><DP>vWnMC7GF7ZVddIc5ybTTYT/NIILPHRplvcOEgY4hQnxiyudU2Rl1KQAdpTTClqFm779tqxJq31jrC5csx5+FXw==</DP><DQ>bt1YAL7j5X1Mof9y/cb75396Dog3W2+Wuw+GeIZebPsOttOTqbt76mAYJrBZCK3QDofxf1mvkHnPiFDSEHdmUQ==</DQ><InverseQ>NFCFTyPQkHE88ObRztW7hh9EwPRStFEcdXGTEsqpjaSXvzDkFTYv3xRfBi4KzGH6XHX5Rtzs3q7tBBCke5SqVQ==</InverseQ><D>puPKoA1clsihCrDYbulPjw1pZAMS45T3lV2pi+YqfuW8aJzDD7325EDZUm/MyZafIc/0FFuILPW2y0r4Sq4fcpfhE5O8qqm0ejya2xZ0YJI2OR4CS60o14oU+TGUvwbkj9wZPwZ4bf8WmK5lqwwonhXNQBMXZ/k3yaVP8kK2PSE=</D></RSAKeyValue>";
        /// <summary>
        /// 公钥
        /// </summary>
        private const string publicKey = "<RSAKeyValue><Modulus>yJlVOeahWIm8qv0Q65fyl58OVhyd3UrkwpfH2lyxsHvHzOJmaeP5BiPcG0/7wxdiqDvAFzYNrLdkgBfBcvyl9OzAOTV25wQPygRAzydfqclFP/Qsx1BnzBp/RvjNIyqKavaqpVijAAhaCfFE+Gv22FeT9PkFEYmuDK82cTIyYFs=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
        /// <summary>
        /// 隐藏窜
        /// </summary>
        private const string HideKey = "_ymatou_com";

        /// <summary>
        /// AES加密的Token长度
        /// </summary>
        private const int TokenLength = 112;

        /// <summary>
        /// 原始窜
        /// </summary>
        [BsonElement("tId")]
        public string RawToken { get; private set; }
        /// <summary>
        /// 加密后的窜
        /// </summary>
        [BsonElement("sId")]
        public string TokenId { get; private set; }
        [BsonElement("uId")]
        public int UserId { get; private set; }
        [BsonElement("eTtime")]
        [BsonIgnoreIfDefault]
        [BsonIgnoreIfNull]
        public DateTime? ExpiredTime { get; private set; }
        [BsonElement("cTime")]
        [BsonIgnoreIfDefault]
        public DateTime CreateTime { get; private set; }
        [BsonElement("tSource")]
        [BsonIgnoreIfNull]
        public string TokenSource { get; private set; }
        [BsonElement("tContext")]
        [BsonIgnoreIfNull]
        public string TokenContext { get; private set; }

        [BsonIgnore]
        public string _originalVal { get; private set; }
        private string _sign;

        [BsonIgnoreIfNull]
        [BsonElement("tLVerifyTime")]
        public DateTime LastUpDateTime { get; private set; }

        /// <summary>
        ///  UserToken
        /// </summary>
        /// <param name="userId">userId</param>
        /// <param name="tokenTimeOut">tokenTimeOut</param>
        /// <param name="tokenSource">tokenSource</param>
        /// <param name="tokenContext">tokenContext</param>
        public UserToken(int userId, long? tokenTimeOut = null, string tokenSource = null, string tokenContext = "login")
        {
            this.Id = Guid.NewGuid().ToString("N");
            this.UserId = userId;
            this.TokenContext = tokenContext;
            this.CreateTime = DateTime.Now;
            this.SetTokenSource(tokenSource);
            this.SetTokenExpiredTime(tokenTimeOut);
        }
        /// <summary>
        /// UserToken
        /// </summary>
        /// <param name="token">经过加密的token</param>
        public UserToken(/*string token*/)
        {
            //this.TokenId = token;
        }
        /// <summary>
        ///  UserToken
        /// </summary>
        /// <param name="originalVal">原始窜</param>
        /// <param name="sign">RSA加密的窜</param>
        public UserToken(string originalVal, string sign)
        {
            this._originalVal = originalVal;
            this._sign = sign;
        }
        /// <summary>
        /// token 是否过期
        /// </summary>
        /// <returns></returns>
        public bool TokenIsExpired()
        {
            if (!this.ExpiredTime.HasValue)
                return false;
            if (this.ExpiredTime.Value.ToLocalTime() > DateTime.Now.ToLocalTime())
                return false;
            return true;
        }
        /// <summary>
        /// 重置token来源标识
        /// </summary>
        /// <param name="source"></param>
        public void ReSetTokenSource(string source)
        {
            this.TokenSource = source;
        }
        /// <summary>
        /// 创建token
        /// </summary>
        public void GenerateToken()
        {
            //userId_guid|_ymatou_com|expiredTime
            var _newId = Guid.NewGuid().ToString("N");
            
            //TODO: 目前仅支持tokenSource是app, wap的按新的token格式解析（买手暂不接入）
            if (YmtAuth.Domain.Model.Token.TokenSource.IsApplyTokenSource(this.TokenSource) || ConfigUtility.EnableTokensource)
            {
                _originalVal = string.Format("{0}|{1}|{2}|{3}", this.UserId, _newId, this.TokenSource, HideKey);
            }
            else
            {
                _originalVal = string.Format("{0}|{1}|{2}", this.UserId, _newId, HideKey);
            }

            //bool error;
            //var _token = EncryptorHelp.RSAGroupingEncryptor(publicKey, _originalVal, Encoding.UTF8, out error);
            var _token = new Encrypt().DesEncrypt(_originalVal, true);
            this.RawToken = _newId;
            this.TokenId = _token;
        }
        public void GenerateToken(string id)
        {
            //userId_guid|_ymatou_com|expiredTime
            //var _newId = Guid.NewGuid().ToString("N");
            var _originalVal = string.Format("{0}|{1}|{2}", this.UserId, id, HideKey);
            //bool error;
            //var _token = EncryptorHelp.RSAGroupingEncryptor(publicKey, _originalVal, Encoding.UTF8, out error);
            var _token = new Encrypt().DesEncrypt(_originalVal, true);
            this.RawToken = id;
            this.TokenId = _token;
        }
        /// <summary>
        /// 验证签名
        /// </summary>    
        /// <returns></returns>
        public Tuple<LoginResponseCode, string, string> VerifySign()
        {
            using (var mm = MethodMonitor.New("VerifySign_RSA"))
            {
                if (string.IsNullOrEmpty(_sign) || string.IsNullOrEmpty(_originalVal))
                    return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.TokenInvalid, "token无效",
                        string.Empty);
                //原始窜：uid|tokenid
                var splitOriginalVal = _originalVal.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                if (splitOriginalVal.Length != 2)
                {
                    YmatouLoggingService.Error("原始窜错误 {0}，{1}", _originalVal, _sign);
                    return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.SignError, "原始窜错误",
                        string.Empty);
                }

                bool signError;
                var encryption = EncryptorHelp.RSAGroupingDecryptor(privateKey, _sign, UTF8Encoding.UTF8, out signError);
                //签名窜：uid|tokenid|_ymatou_com
                if (signError)
                {
                    YmatouLoggingService.Error("RSA解密错误 {0}，{1}，{2}", _originalVal, _sign, encryption);
                    return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.SignError, "RSA解密错误",
                        string.Empty);
                }
                var decryptionVal = encryption.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                if (decryptionVal.Length != 3)
                {
                    YmatouLoggingService.Error("解密后签名错误 {0}，{1}", decryptionVal, _sign);
                    return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.SignError, "解密后签名错误",
                        string.Empty);
                }
                var _tmp = decryptionVal[2];
                if (_tmp.EndsWith("\0"))
                    _tmp = _tmp.Replace("\0", "").Trim();
                if (splitOriginalVal[0] != decryptionVal[0]
                    || splitOriginalVal[1] != decryptionVal[1]
                    || HideKey != _tmp)
                {
                    YmatouLoggingService.Error("签名不匹配 {0}，{1}", encryption, _sign);
                    return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.SignError, "签名不匹配",
                        string.Empty);
                }

                this.UserId = Convert.ToInt32(decryptionVal[0]);
                this.TokenId = decryptionVal[1];
                DecryptToken(this.TokenId);

                if (string.IsNullOrEmpty(this.RawToken))
                    return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.SignError, "Token解密错误", string.Empty);

                //if (this.TokenId.Length > 32)
                //    DecryptToken();
                //else
                //    this.RawToken = decryptionVal[1];

                return Tuple.Create<LoginResponseCode, string, string>(LoginResponseCode.OK, "ok", decryptionVal[1]);
            }
        }
        /// <summary>
        /// 验证token，userid是否匹配。返回 false 表示userID 不匹配
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public bool VerifyTokenUserIsMatch(int userId)
        {
            //if (this.UserId != userId) return Tuple.Create(LoginResponseCode.TokenInvalid, "token userId 不匹配");
            ////if (TokenIsExpired()) return Tuple.Create(LoginResponseCode.TokenExpired, "token过期");
            //return Tuple.Create(LoginResponseCode.OK, "ok");
            return this.UserId == userId;
        }
        /// <summary>
        ///  解密token
        /// </summary>
        /// <returns></returns>
        public void DecryptToken(string tokenId)
        {
            using (var mm = MethodMonitor.New("DecryptToken_AES"))
            {
                try
                {
                    //兼容老的签名算法
                    Guid guid;
                    if (tokenId.Length == 32 && Guid.TryParseExact(tokenId, "N", out guid))
                    {
                        this.RawToken = this.TokenId;
                        return;
                    }

                    int tokenLengthCheck = ConfigurationManager.AppSettings["TokenLengthCheck"].ConvertToInt32(0);

                    if (tokenLengthCheck == 1 && tokenId.Length < TokenLength)
                    {
                        using (var mm1 = MethodMonitor.New("DecryptToken_AES_ResultFail"))
                        {

                        }
                        //TODO： 解密失败，前端传的token不对
                        YmatouLoggingService.Info("DecryptToken 解密token {0}, TokenLength not match, actual: {1}, expected: 不小于{2}", tokenId, tokenId.Length, TokenLength);
                        this.TokenId = string.Empty;
                        this.RawToken = string.Empty;
                        return;
                    }

                    var token = new Encrypt().DesDecrypt(tokenId, true);
                    var tokenArray = token.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);

                    if (tokenArray == null || !(tokenArray.Length == 3 || tokenArray.Length == 4))
                    {
                        this.TokenId = string.Empty;
                        this.RawToken = string.Empty;
                        return;
                    }

                    if (tokenArray.Length == 3)
                    {
                    if (tokenArray[2] != HideKey) return;
                    this.RawToken = tokenArray[1];
                    this.UserId = Convert.ToInt32(tokenArray[0]);

                        using (var mm1 = MethodMonitor.New("DecryptToken_OldFormat"))
                        {

                        }
                    }
                    else if (tokenArray.Length == 4)
                    {
                        if (tokenArray[3] != HideKey) return;
                        this.TokenSource = tokenArray[2];
                        this.RawToken = tokenArray[1];
                        this.UserId = Convert.ToInt32(tokenArray[0]);

                        YmatouLoggingService.Info("DecryptToken, TokenSource: {0}, tokenId: {1}, UserId: {2}, RawToken: {3}", tokenArray[2], tokenId, this.UserId, tokenArray[1]);

                        using (var mm1 = MethodMonitor.New("DecryptToken_NewFormat"))
                        {

                        }
                    }
                }
                catch (Exception ex)
                {
                    using (var mm1 = MethodMonitor.New("DecryptToken_AES_ResultException"))
                    {

                    }

                    YmatouLoggingService.Error(string.Format("DecryptToken 解密token {0} 异常 ", tokenId), ex);
                    this.RawToken = string.Empty;
                    this.UserId = -1;
                }
            }
        }

        /// <summary>
        /// 生成加密后的token
        /// </summary>
        /// <returns></returns>
        public string EncryptToken()
        {
            if (string.IsNullOrEmpty(this.TokenId))
            {
                GenerateToken();
            }
            return this.TokenId;
        }
        /// <summary>
        /// 设置token过期时间
        /// </summary>
        /// <param name="tokenTimeOut"></param>
        public void SetTokenExpiredTime(long? tokenTimeOut)
        {
            if (tokenTimeOut.HasValue && tokenTimeOut.Value > 0)
                this.ExpiredTime = DateTime.Now.Add(TimeSpan.FromTicks(tokenTimeOut.Value));
            else
            {
                var defTokenTimeOut = System.Configuration.ConfigurationManager.AppSettings["tokenDefaultTimeOut"].ConvertToInt32(7);
                this.ExpiredTime = DateTime.Now.Add(TimeSpan.FromDays(defTokenTimeOut));//取配置时间
            }
        }

        /// <summary>
        /// 延长token过期时间。返回 true 表示需要延长过期时间，false 则不需要延长过期时间
        /// </summary>
        /// <param name="source"></param>
        public bool IsExtendTokenExpiredTime(string source)
        {
            if (!TokenIsExpired()) return false;
            if (string.IsNullOrEmpty(source))
            {
                this.ExpiredTime = (DateTime?)this.ExpiredTime.Value.ToLocalTime().AddDays(1);
                return true;
            }
            var timeOutStr = ConfigurationManager.AppSettings["ExtendTokenTimeOut"].Split(new char[] { ';' });
            try
            {
                //lock (lockObj)
                //{
                var dic2 = new Dictionary<string, string>();
                foreach (var item in timeOutStr)
                {
                    dic2.TryAddOrSet(item.Split(new char[] { ':' })[0], item.Split(new char[] { ':' })[1]);
                }
                var tmpSource = source.ToLower();
                //如果没有配置延期token的应用，则无需延期
                //if (!dic2.ContainsKey(tmpSource))
                //    return false;
                //
                //var dic = timeOutStr.ToDictionary(e => e.Split(new char[] { ':' })[0], e => e.Split(new char[] { ':' })[1]);
                //
                var val = dic2.TryGetV(tmpSource, "1").ConvertToInt32(1);
                this.ExpiredTime = (DateTime?)this.ExpiredTime.Value.ToLocalTime().AddDays(val);
                //}
                return true;
            }
            catch (Exception ex)
            {
                YmatouLoggingService.Error("延长token过期时间错误 {0}", ex.ToString());
                return false;
            }
        }
        public static bool EnableCheckSign()
        {
            return ConfigurationManager.AppSettings["signOpen"] != "1";
        }
        private void SetTokenSource(string tokenSource)
        {
            if (string.IsNullOrEmpty(tokenSource)) return;
            this.TokenSource = tokenSource.ToLower();
            //老的业务没有使用 Ymatou.User.Common.UserEnum.UserRegSource 用户应用来源作为tokenSource，
            //而是使用域名作为来源，为了兼容老的业务
            //所以这里作为分支判断
            //if (tokenSource == "商家后台登录" || tokenSource == "seller.ymatou.com" || tokenSource == "SellerSite") tokenSource = "sellersite";
            //if (_ts == "www.ymatou.com" || _ts == "www.alpha.ymatou.com") this.TokenSource = "mainsite";
            //else this.TokenSource = _ts;
        }

        public static bool EnableCheckSignTokenLog()
        {
            if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["CheckSignTokenWriteLog"])) return false;
            return (ConfigurationManager.AppSettings["CheckSignTokenWriteLog"] == "1");
        }

        protected override IEnumerable<object> CompareValues()
        {
            yield return this.Id;
        }
    }
}
